package com.sunday.authorization.core.controller;

import com.sunday.authorization.core.userdetails.CustomUserDetailsService;
import com.sunday.authorization.core.utils.VerifyCode;
import com.sunday.authorization.core.vo.LoginCaptchaReqVO;
import com.sunday.authorization.security.constants.SecurityConstants;
import com.sunday.common.core.asserts.BusinessAssert;
import com.sunday.common.core.enums.ErrorCodeEnum;
import com.sunday.common.core.exception.BusinessException;
import com.sunday.common.core.remoting.rest.annotation.OpenApi;
import com.sunday.common.core.remoting.rest.annotation.VoErrorShowType;
import com.sunday.common.core.remoting.rest.response.RestResponse;
import com.sunday.redis.lua.core.RedisLuaTemplate;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Stream;


/**
 * 登录必要条件
 *
 * @author zsy
 * @since 2024/3/18
 */
@Slf4j
@RestController
@RequestMapping("/")
@RequiredArgsConstructor
public class LoginController {

    private final RedisLuaTemplate redisLuaTemplate;

    private final PasswordEncoder passwordEncoder;

    private final CustomUserDetailsService customUserDetailsService;

    /**
     * 图片验证码
     *
     * @param request
     * @param response
     * @return
     */
    @OpenApi
    @GetMapping(SecurityConstants.VERIFY_CODE_PATH_URL)
    public void verifyCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        /** 判定图片验证码 cookie 是否存在 */
        String sequenceId = Optional.ofNullable(request.getCookies())
                .flatMap(cookies -> Stream.of(cookies)
                        .filter(cookie -> SecurityConstants.COOKIE_VERIFY_CODE_KEY.equals(cookie.getName()))
                        .findFirst()
                        .map(cookie -> cookie.getValue())
                )
                .orElseGet(() -> {
                    String value = UUID.randomUUID().toString();
                    Cookie captchaSessionKey = new Cookie(SecurityConstants.COOKIE_VERIFY_CODE_KEY, value);
                    captchaSessionKey.setPath("/");
                    response.addCookie(captchaSessionKey);
                    return value;
                });

        /** 组织图片验证 */
        VerifyCode vc = new VerifyCode();
        BufferedImage image = vc.getImage(); // 生成图片
        redisLuaTemplate.setEx(SecurityConstants.loginImageVerifyCode(sequenceId), vc.getText(), 300l);

        response.setContentType(MediaType.IMAGE_JPEG_VALUE);
        ServletOutputStream out = response.getOutputStream();
        VerifyCode.output(image, out);
        out.flush();
    }


    /**
     * 手机验证码
     *
     * @param loginCaptchaReqVo
     * @return {@link com.sunday.common.core.remoting.rest.response.RestResponse<?>}
     */
    @OpenApi
    @VoErrorShowType(RestResponse.ShowTypeEnum.ERROR_MESSAGE)
    @PostMapping(SecurityConstants.CAPTCHA_PATH_URL)
    public RestResponse captcha(@RequestBody LoginCaptchaReqVO loginCaptchaReqVo) {

        try {

            /**
             * 优先判定用户请求手机验证码是否超过次数
             */
            Long currCount = redisLuaTemplate.get(SecurityConstants.loginErrorMaxCountKey(loginCaptchaReqVo.getUsername()), Long.class);
            BusinessAssert.isTrue(currCount == null || currCount < SecurityConstants.LOGIN_ERROR_MAX_COUNT_NUMBER, ErrorCodeEnum.A0_241);

            /**
             * 判定用戶是否合法
             */
            return Optional.ofNullable(customUserDetailsService.getUser(loginCaptchaReqVo.getUsername()))
                    .filter(userDetails -> passwordEncoder.matches(loginCaptchaReqVo.getPassword(), userDetails.getPassword()))
                    .map(_ -> {
                        //模拟为验证码五分钟有效
                        String captcha = redisLuaTemplate.get(SecurityConstants.loginPhoneCaptcha(loginCaptchaReqVo.getUsername()));
                        if (captcha == null) {
                            captcha = String.valueOf((int) ((Math.random() * 9 + 1) * 100000));
                            redisLuaTemplate.setEx(SecurityConstants.loginPhoneCaptcha(loginCaptchaReqVo.getUsername()), captcha, 300l);
                        }
                        // TODO 后续进行验证码发送 captcha -> send sms
                        return RestResponse.ok();
                    })
                    // A0_220(ErrorClassifyEnum.A + "0220","用户身份校验失败")
                    .orElseThrow(() -> new BusinessException(ErrorCodeEnum.A0_220, "账户或密码错误"));

        } catch (Exception cause) {
            /**
             * 符合业务级别异常，将进行额外的计数处理
             */
            if (cause instanceof BusinessException exception) {
                long count = redisLuaTemplate.incrWithinPeriod(SecurityConstants.loginErrorMaxCountKey(loginCaptchaReqVo.getUsername()), 7200L);
                // A0_221(ErrorClassifyEnum.A + "0221","账户或密码错误"),
                // 当前失败次数 <= 10 && >= 6
                BusinessAssert.state(count <= SecurityConstants.LOGIN_ERROR_MAX_COUNT_NUMBER && count >= SecurityConstants.LOGIN_ERROR_WARN_COUNT_NUMBER, () -> {
                    throw new BusinessException(exception.getCode(), exception.getMessage(), STR."您还有\{SecurityConstants.LOGIN_ERROR_MAX_COUNT_NUMBER - count}次尝试机会", true);
                });
            }
            throw cause;
        }

    }

}
